package cn.demoncat.util.redis.util;

import cn.demoncat.util.lang.JsonUtil;
import cn.demoncat.util.lang.LocalDateUtil;
import cn.demoncat.util.lang.constant.StringConstant;
import cn.demoncat.util.lang.entity.ListStr;
import cn.demoncat.util.mybatis.util.TransactionUtil;
import cn.demoncat.util.redis.config.RedisIsolator;
import cn.demoncat.util.redis.entity.RedisKey;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * Redis常用工具
 *
 * 注：为了跟踪项目中使用了哪些键，必须使用RedisKey对象来声明缓存常量
 * 注：为了跟踪项目中使用了哪些锁，必须使用RedisLock对象来声明锁的缓存常量
 * 注：为了跟踪项目中使用了哪些序列，必须使用RedisSeq对象来声明序列的缓存常量
 * 注：为了跟踪项目中使用了哪些延时队列，必须使用RedisDelay对象来声明延时队列的缓存常量
 * 注：多个系统共用Redis时，为防止KEY冲突，可以通过 RedisIsolator 来拼接前缀，实现数据隔离
 *
 * @author 延晓磊
 *
 * @since 2019年5月29日
 *
 * @see <a href="http://redis.cn/">Redis中文</a>
 * @see <a href="https://docs.spring.io/spring-data/redis/docs/current/reference/html/">Spring Data Redis</a>
 */
@Component
public class RedisUtil {

	/**
	 * Spring-Data的Redis操作模板
	 */
	@Autowired
	private StringRedisTemplate stringRedisTemplate;
	/**
	 * 配置文件
	 */
	@Autowired
	private RedisProperties redisProperties;
	/**
	 * Redisson Util
	 */
	@Resource
	private RedissonUtil redissonUtil;
	/**
	 * Redis Lock Util
	 */
	@Resource
	private RedisLockUtil redisLockUtil;
	/**
	 * Redis Seq Util
	 */
	@Resource
	private RedisSeqUtil redisSeqUtil;
	/**
	 * Redis Num Util
	 */
	@Resource
	private RedisNumUtil redisNumUtil;
	/**
	 * Redis Map Util
	 */
	@Resource
	private RedisMapUtil redisMapUtil;
	/**
	 * Redis Set Util
	 */
	@Resource
	private RedisSetUtil redisSetUtil;
	/**
	 * Redis Delay Util
	 */
	@Resource
	private RedisDelayUtil redisDelayUtil;
	/**
	 * Redis Execute Util
	 */
	@Resource
	private RedisExecuteUtil redisExecuteUtil;

	@Resource
	private RedisIsolator redisIsolator;

	/**
	 * 缓存穿透：缓存不存在时，临时缓存空数据来防止恶意穿透的过期时间，可以全局修改
	 */
	public static int TEMP_EXP = 60 * 30;

	/* ================================= 基础操作 ========================================= */


	/**
	 * 获取Spring-Data的Redis配置
	 *
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public RedisProperties getProperties() {
		return redisProperties;
	}

	/**
	 * 获取Spring-Data的Redis操作模板
	 *
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public StringRedisTemplate getTemplate() {
		return stringRedisTemplate;
	}

	/**
	 * 获取Redisson操作工具
	 *
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月13日
	 */
	public RedissonUtil getRedisson(){
		return redissonUtil;
	}

	/**
	 * 获取脚本执行工具
	 *
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月13日
	 */
	public RedisExecuteUtil getExecute(){
		return redisExecuteUtil;
	}

	/**
	 * 获取Redis的序列操作工具
	 *
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月13日
	 */
	public RedisSeqUtil getSeq(){
		return redisSeqUtil;
	}

	/**
	 * 获取Redis的数值操作工具
	 *
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月13日
	 */
	public RedisNumUtil getNum(){
		return redisNumUtil;
	}

	/**
	 * 获取Redis的Map操作工具
	 *
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月13日
	 */
	public RedisMapUtil getMap(){
		return redisMapUtil;
	}

	/**
	 * 获取Redis的Set操作工具
	 *
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月13日
	 */
	public RedisSetUtil getSet(){
		return redisSetUtil;
	}

	/**
	 * 获取Redis的锁操作工具
	 *
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月13日
	 */
	public RedisLockUtil getLock(){
		return redisLockUtil;
	}

	/**
	 * 获取Redis的延时队列工具
	 *
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月13日
	 */
	public RedisDelayUtil getDelay(){
		return redisDelayUtil;
	}

	/**
	 * 获取隔离器（通过前缀隔离缓存KEY）
	 *
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2022年09月29日
	 */
	public RedisIsolator getIsolator(){
		return redisIsolator;
	}

	/**
	 * 是否为集群
	 *
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月10日
	 */
	public boolean isCluster(){
		// 单机：spring.redis.url=redis://ip:port
		// 集群：spring.redis.cluster.nodes=ip:port,ip:port
		return StringUtils.isBlank(redisProperties.getUrl());
	}

	/**
	 * 执行脚本
	 *
	 * 1、减少网络开销：在Lua脚本中同时执行多条命令
	 * 2、原子操作：Redis会将整个脚本作为一个整体执行，中间不会被其他命令插入
	 * 3、流程控制：可以添加流程和判断控制
	 *
	 * 语法：EVAL "脚本" 1 key val...
	 * 命令：在脚本中，可以通过 redis.call('DEL',KEYS[1]) 的方式调用Redis命令
	 * 取值：在脚本中，可以通过 KEYS[1] 获取key参数，通过 ARGV[1] ARGV[2] 获取val参数
	 *
	 * @param clazz		返回类型：数值 long，布尔 boolean，字符串 String
	 * @param script	LUA脚本
	 * @param key		KEY
	 * @param args		参数
	 *                  
	 * @author 延晓磊
	 * @since 2021年02月08日
	 *
	 * @see <a href="https://www.runoob.com/lua/lua-tutorial.html">LUA</a>
	 */
	public <R>R execute(Class<R> clazz, String script, String key, Object ... args){
		return getExecute().execute(clazz, script, key, args);
	}

	/**
	 * 执行脚本 - 无返回
	 *
	 * @param script	LUA脚本
	 * @param key		KEY
	 * @param args		参数
	 *
	 * @author 延晓磊
	 * @since 2021年02月08日
	 */
	public void execute(String script, String key, Object ... args){
		getExecute().execute(script, key, args);
	}

	/**
	 * 执行命令（通过管道来复用连接/批量执行）
	 *
	 * 原理：执行流程 > 将redis命令缓存，并返回null > 批量执行redis命令并返回结果
	 *
	 * 1、runnable中，可以包含redis命令和普通代码
	 * 2、runnable中，获取到的redis命令结果都是null
	 * 3、return中，返回每条redis命令的结果
	 *
	 * @param runnable	批量任务：fn(连接)
	 *
	 * @return 每条命令的结果
	 *
	 * @author 延晓磊
	 * @since 2022年08月25日
	 */
	public List<Object> execute(Consumer<StringRedisConnection> runnable){
		return getTemplate().executePipelined((RedisCallback<Object>) redisConnection -> {
			runnable.accept((StringRedisConnection) redisConnection);
			return null;
		});
	}

	/**
	 * 获取Redis String
	 *
	 * 查看		get()
	 * 添加		set()
	 * 不存在时添加 	setIfAbsent(v)
	 * 存在时添加		setIfPresent(v)
	 * 递增		increment()
	 * 递减		decrement()
	 *
	 * @param key
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月10日
	 */
	public BoundValueOperations<String, String> str(String key){
		return getTemplate().boundValueOps(redisIsolator.getKey(key));
	}

	/**
	 * 获取Redis List，不存在时创建
	 *
	 * 尾部添加	rightPush(e)
	 * 尾部移除	rightPop()		队列为空时返回null
	 * 头部添加	leftPush(e)
	 * 头部移除	leftPop()		队列为空时返回null
	 * 索引插入  	set(i, v)
	 * 查找索引 	index(v)
	 * 查看列表	range(0, -1)
	 * 索引区间	range(start, end)
	 * 删除区间	trim(start, end)
	 * 列表长度	size()
	 *
	 * @param key
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月10日
	 */
	public BoundListOperations<String, String> list(String key){
		return getTemplate().boundListOps(redisIsolator.getKey(key));
	}

	/**
	 * 获取Redis Set
	 *
	 * 1、如果set有元素，exists 为true，否则为false
	 *
	 * 添加元素		add(e)		Set不存在时创建
	 * 获取集合		members()	Set不存在时返回[]
	 * 集合长度		size()
	 * 删除元素		remove(e)
	 * 随机获取元素	randomMember()
	 * 随机移除元素	pop()
	 * 是否包含元素	isMember(e)	Set不存在返回false
	 *
	 * @param key
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月10日
	 */
	public BoundSetOperations<String, String> set(String key){
		return getSet().set(redisIsolator.getKey(key));
	}

	/**
	 * 获取Redis SortedSet，不存在时创建
	 *
	 * <pre>
	 *
	 * 添加元素		add(e,score)
	 * 删除元素		remove(e)
	 * 				removeRange(index,index)
	 * 				removeRangeByScore(score,score)
	 * 查看列表		range(0, -1)
	 * 获取索引		rank(v)
	 * 索引区间		range(start, end)
	 * 				rangeByLex(RedisZSetCommands.Range.range().lte(1))
	 * 获取评分		score(v)
	 * 评分区间		rangeByScore(min,max)
	 *
	 * 命令：
	 *     元素个数 ZCARD key
	 *     删除元素 ZREM key e
	 *     查询元素 ZRANGE key 0 -1
	 *     查看分值 ZSCORE key e
	 *     添加元素 ZADD key score e
	 * </pre>
	 *
	 * @param key
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月10日
	 */
	public BoundZSetOperations<String, String> sortedSet(String key){
		return getTemplate().boundZSetOps(redisIsolator.getKey(key));
	}

	/**
	 * 获取Redis Map，不存在时创建
	 *
	 * 添加元素		put(k,v)
	 * 不存在时添加	putIfAbsent(k, v)
	 * 获取元素		get(k)
	 * 删除元素		delete(k)
	 * 查看集合		entries()
	 * 是否包含元素	hasKey(k)
	 *
	 * @param key
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月10日
	 */
	public BoundHashOperations<String, String, String> map(String key){
		return getMap().map(redisIsolator.getKey(key));
	}

	/* ================================= 通用查询 ========================================= */
	
	/**
	 * 正则查询Keys
	 * 
	 * @param pattern  *表示多个任意字符，如 data*表示所有data后缀的key
	 * @return Key的集合
	 */
	public Set<String> keys(String pattern) {
		return getTemplate().keys(redisIsolator.getKey(pattern));
	}
	
	/**
	 * 获取缓存的类型
	 * 
	 * @param key Key
	 * @return DataType {none/string/list/set/zset(SortedSet)/hash(HashMap)}
	 */
	public DataType type(String key) {
		return getTemplate().type(redisIsolator.getKey(key));
	}

	/**
	 * 判断缓存是否存在
	 * 
	 * @param key Key
	 * @return true/false
	 */
	public boolean exists(String key) {
		return Boolean.TRUE.equals(getTemplate().hasKey(redisIsolator.getKey(key)));
	}
	
	/* ========================= 过期时间 =========================== */

	/**
	 * 设置缓存的存活时间
	 * 
	 * 缓存时间只能针对单个数据，不能针对集合的元素
	 * 所以集合类型数据要设置时间，需使用String保存，使用相同前缀的Key作区分（使用 :分隔符便于索引）
	 * Set和Del方法会清除过期时间的设置；过期的原理是在Get时检查时间并删除，以便减少CPU压力，但内存中会有很多过期数据
	 * 
	 * @param key Key
	 * @param second n秒后,null或不大于0表示永久
	 * @return 成功失败
	 */
	public boolean expire(String key, Integer second) {
		if (second == null || second <= 0) {
			return expirePersist(key);
		}
		return Boolean.TRUE.equals(getTemplate().expire(redisIsolator.getKey(key), second, TimeUnit.SECONDS));
	}

	/**
	 * 设置缓存的过期时间
	 * 
	 * @param 	key Key
	 * @return 	time 时间戳
	 * @return 	成功失败
	 */
	public boolean expireAt(String key, long time) {
		return Boolean.TRUE.equals(getTemplate().expireAt(redisIsolator.getKey(key), new Date(time)));
	}

	/**
	 * 设置缓存的过期时间
	 * 
	 * @param 	key Key
	 * @return 	date 时间
	 * @return 	成功失败
	 */
	public boolean expireAt(String key, Date date) {
		return Boolean.TRUE.equals(getTemplate().expireAt(redisIsolator.getKey(key), date));
	}

	/**
	 * 设置缓存的过期时间
	 * 
	 * @param 	key Key
	 * @return 	date 时间
	 * @return 	成功失败
	 */
	public boolean expireAt(String key, LocalDateTime date) {
		return Boolean.TRUE.equals(getTemplate().expireAt(redisIsolator.getKey(key), LocalDateUtil.toDate(date)));
	}
	
	/**
	 * 设置缓存的过期时间为永久，即移除缓存的过期设置
	 * 
	 * @param key Key
	 * @return 成功失败
	 */
	public boolean expirePersist(String key) {
		return Boolean.TRUE.equals(getTemplate().persist(redisIsolator.getKey(key)));
	}

	/**
	 * 查看缓存过期时间的剩余秒数
	 * 
	 * @param 	key Key
	 * @return 剩余过期秒数
	 */
	public Long ttl(String key) {
		return getTemplate().getExpire(redisIsolator.getKey(key), TimeUnit.SECONDS);
	}

	/**
	 * 查看缓存过期剩余时间
	 * 
	 * @param 	key Key
	 * @param	timeUnit 时间单位
	 * 
	 * @return 剩余过期时间
	 */
	public Long ttl(String key, TimeUnit timeUnit) {
		return getTemplate().getExpire(redisIsolator.getKey(key), timeUnit);
	}
	
	/* ================================= 删除 ========================================= */
	
	/**
	 * 删除缓存
	 * 
	 * @param key Key...
	 * @return 删除数
	 */
	public long del(String ...key) {
		if (key == null || key.length == 0) {
			return 0;
		}else if (key.length == 1) {
			return Boolean.TRUE.equals(getTemplate().delete(redisIsolator.getKey(key[0]))) ? 1 : 0;
		}else {
			return del(Arrays.asList(key));
		}
	}

	/**
	 * 删除缓存
	 * 
	 * @param keys Key...
	 * @return 删除数大于0返回true
	 */
	public Long del(Collection<String> keys) {
		return getTemplate().delete(redisIsolator.getKeys(keys));
	}

	/**
	 * 删除缓存
	 * 
	 * @param keyPre	KEY前缀
	 * @param ids		数据ID，key = keyPre+id
	 * 
	 * @return 删除数大于0返回true
	 */
	public <E>Long del(String keyPre, Collection<E> ids) {
		return del(ids.stream().map(id -> keyPre + id).collect(Collectors.toList()));
	}

	/**
	 * 校验当前值并删除
	 *
	 * @param key
	 * @param val	当前值，相等时删除
	 * @return
	 */
	public boolean delVal(String key, String val){
		return 1 == execute(Long.class, "if redis.call('GET',KEYS[1]) == ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end", key, val);
	}

	/**
	 * 前缀删除
	 *
	 * @param keyPre	键前缀
	 * @return
	 */
	public Long delPre(String keyPre){
		Set<String> keys = keys(keyPre + "*");
		return getTemplate().delete(keys);
	}

	/**
	 * 删除缓存 - 事务提交后
	 * 
	 * @param key Key...
	 */
	public void txDel(String ...key) {
		TransactionUtil.afterCommit(() -> del(key));
	}
	
	/**
	 * 删除缓存 - 事务提交后
	 * 
	 * @param keys Key...
	 */
	public void txDel(Collection<String> keys) {
		TransactionUtil.afterCommit(() -> del(keys));
	}

	/**
	 * 删除缓存 - 事务提交后
	 * 
	 * @param keyPre	KEY前缀
	 * @param ids		数据ID，key = keyPre+id
	 */
	public <E>void txDel(String keyPre, Collection<E> ids) {
		TransactionUtil.afterCommit(() -> del(keyPre, ids));
	}

	/**
	 * 校验当前值并删除 - 事务提交后
	 *
	 * @param key
	 * @param val	当前值，相等时删除
	 * @return
	 */
	public void txDelVal(String key, String val){
		TransactionUtil.afterCommit(() -> delVal(key, val));
	}

	/**
	 * 前缀删除 - 事务提交后
	 *
	 * @param keyPre	键前缀
	 * @return
	 */
	public void txDelPre(String keyPre){
		TransactionUtil.afterCommit(() -> delPre(keyPre));
	}

	/* ========================= 字符串(key:value) =========================== */

	/**
	 * 设置字符串：如果存在则设置，不存在则创建
	 * 
	 * @param key
	 * @param value
	 */
	public void set(String key, String value) {
		getTemplate().opsForValue().set(redisIsolator.getKey(key), value);
	}

	/**
	 * 设置字符串 - 事务提交后：如果存在则设置，不存在则创建
	 * 
	 * @param key
	 * @param value
	 */
	public void txSet(String key, String value) {
		TransactionUtil.afterCommit(() -> set(key, value));
	}
	
	/**
	 * 批量设置字符串：如果存在则设置，不存在则创建
	 * 
	 * @param kvs 键值对集
	 */
	public void set(Map<String, String> kvs) {
		getTemplate().opsForValue().multiSet(redisIsolator.getKeyVals(kvs));
	}

	/**
	 * 设置字符串 - 事务提交后：如果存在则设置，不存在则创建
	 *
	 * @param kvs 键值对集
	 */
	public void txSet(Map<String, String> kvs) {
		TransactionUtil.afterCommit(() -> set(kvs));
	}

	/**
	 * 设置字符串：SET k v NX
	 * 
	 * 1、如果不存在则设置，存在则忽略并返回false
	 * 
	 * @param key
	 * @param value
	 * @return 成功失败
	 */
	public Boolean setNx(String key, String value) {
		return getTemplate().opsForValue().setIfAbsent(redisIsolator.getKey(key), value);
	}

	/**
	 * 批量设置字符串：SET k v NX
	 * 
	 * 1、如果不存在则设置，存在则忽略并返回false
	 * 
	 * @param kvs 键值对集
	 */
	public Boolean setNx(Map<String, String> kvs) {
		return getTemplate().opsForValue().multiSetIfAbsent(redisIsolator.getKeyVals(kvs));
	}

	/**
	 * 设置字符串：指定过期时间，如果存在则设置，不存在则创建
	 * 
	 * @param key
	 * @param value
	 * @param seconds 过期秒数，null或不大于0表示永久
	 */
	public void setEx(String key, String value, Integer seconds) {
		if (seconds == null || seconds <= 0) {
			this.set(key, value);
		} else {
			getTemplate().opsForValue().set(redisIsolator.getKey(key), value, seconds, TimeUnit.SECONDS);
		}
	}

	/**
	 * 设置字符串：指定过期时间，如果存在则设置，不存在则创建
	 *
	 * @param key
	 * @param value
	 */
	public void setEx(RedisKey key, String value) {
		setEx(key.getKey(), value, key.getExp());
	}

	/**
	 * 设置字符串 - 事务提交后：指定过期时间，如果存在则设置，不存在则创建
	 * 
	 * @param key
	 * @param value
	 * @param seconds 过期秒数，null或不大于0表示永久
	 */
	public void txSetEx(String key, String value, Integer seconds) {
		TransactionUtil.afterCommit(() -> setEx(key, value, seconds));
	}

	/**
	 * 设置字符串 - 事务提交后：指定过期时间，如果存在则设置，不存在则创建
	 *
	 * @param key
	 * @param value
	 */
	public void txSetEx(RedisKey key, String value) {
		TransactionUtil.afterCommit(() -> setEx(key, value));
	}
	
	/**
	 * 设置字符串：SET k v EX seconds NX
	 *
	 * 1、如果存在则返回false，如果不存在则创建并返回true
	 * 2、设置过期时间
	 *
	 * @param key
	 * @param value
	 * @param seconds	null或不大于0表示永久
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年8月28日
	 */
	public boolean setNex(String key, String value, Integer seconds) {
		if (seconds == null || seconds <= 0) {
			return setNx(key, value);
		}
		return Boolean.TRUE.equals(getTemplate().opsForValue().setIfAbsent(redisIsolator.getKey(key), value, seconds, TimeUnit.SECONDS));
	}

	/**
	 * 获取字符串，同时设置新值
	 * 
	 * @param key Key
	 * @param value 新value
	 * @return 旧value
	 */
	public String getSet(String key, String value) {
		return getTemplate().opsForValue().getAndSet(redisIsolator.getKey(key), value);
	}

	/**
	 * 获取字符串，同时删除缓存
	 *
	 * @param key
	 * @return
	 */
	public String getDel(String key){
		return execute(String.class, "local v = redis.call('GET',KEYS[1]) if(v ~= nil) then redis.call('DEL',KEYS[1]) end return v", key);
	}

	/**
	 * 查询字符串
	 * 
	 * @param key
	 */
	public String get(String key) {
		return getTemplate().opsForValue().get(redisIsolator.getKey(key));
	}

	/**
	 * 查询对象
	 * 
	 * @param clazz		序列化对象
	 * @param key
	 */
	public <T>T get(Class<T> clazz, String key) {
		// 查询
		String str = get(key);
		// 反序列化
		if (StringUtils.isBlank(str)) {
			return null;
		}
		return JsonUtil.parse(str, clazz);
	}
	
	/**
	 * 批量查询字符串
	 * 
	 * @param keys Keys
	 * @return Values
	 */
	public List<String> get(Collection<String> keys) {
		if (CollectionUtils.isEmpty(keys)) {
			return new ArrayList<>();
		}
		return getTemplate().opsForValue().multiGet(redisIsolator.getKeys(keys));
	}
	
	/**
	 * 批量查询字符串
	 * 
	 * @param keys Keys
	 * @return Values
	 */
	public <T>List<T> get(Class<T> clazz, Collection<String> keys) {
		// 查询缓存
		List<String> list = get(keys);
		// 反序列化
		if (CollectionUtils.isEmpty(list)) {
			return new ArrayList<>();
		}else {
			return list.stream().map(s -> StringUtils.isBlank(s) ? null : JsonUtil.parse(s, clazz)).collect(Collectors.toList());
		}
	}
	
	/**
	 * 批量查询字符串
	 * 
	 * @param keyPre
	 * @param ids		数据集：key = keyPre + id
	 * @return Values
	 */
	public <E>List<String> get(String keyPre, List<E> ids) {
		if (CollectionUtils.isEmpty(ids)) {
			return new ArrayList<>();
		}
		return get(ids.stream().map((item) -> keyPre + item).collect(Collectors.toList()));
	}

	/**
	 * 批量查询对象
	 * 
	 * @param clazz		序列化对象
	 * @param keyPre	键前缀
	 * @param ids		数据集：key = keyPre + id
	 * @return Values
	 */
	public <E,T>List<T> get(Class<T> clazz, String keyPre, List<E> ids) {
		if (CollectionUtils.isEmpty(ids)) {
			return new ArrayList<>();
		}
		return get(clazz, ids.stream().map((item) -> keyPre + item).collect(Collectors.toList()));
	}

	/* ======================= 容错查询 ======================== */
	
	/**
	 * 查询并缓存字符串
	 * 
	 * 1、缓存不存在时，执行 query()查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 * 
	 * @param key		缓存KEY
	 * @param keyExp	缓存时间
	 * @param query		缓存不存在时的查询器
	 * 
	 * @return str
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月30日
	 */
	public String getStr(String key, int keyExp, Supplier<String> query) {
		// 查询缓存
		String json = get(key);
		// 查询DB
		if (json == null) {
			// 查询并存入缓存
			json = query.get();
			if (json == null) {
				json = StringConstant.EMPTY;
				setEx(key, json, TEMP_EXP);
			}else {
				setEx(key, json, keyExp);
			}
		}
		return json;
	}
	
	/**
	 * 查询并缓存字符串
	 * 
	 * 1、缓存不存在时，执行 query()查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 * 
	 * @param key		缓存KEY和过期时间
	 * @param query		缓存不存在时的查询器
	 * 
	 * @return str
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月30日
	 */
	public String getStr(RedisKey key, Supplier<String> query) {
		return getStr(key.getKey(), key.getExp(), query);
	}
	
	/**
	 * 查询并缓存字符串
	 * 
	 * 1、缓存不存在时，执行 query(param)查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 * 
	 * @param key		缓存KEY
	 * @param keyExp	缓存时间
	 * @param param		缓存不存在时的查询参数，如id
	 * @param query		缓存不存在时的查询器
	 * 
	 * @return str
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月30日
	 */
	public <P>String getStr(String key, int keyExp, P param, Function<P,String> query) {
		// 查询缓存
		String json = get(key);
		// 查询DB
		if (json == null) {
			// 查询并存入缓存
			json = query.apply(param);
			if (json == null) {
				json = StringConstant.EMPTY;
				setEx(key, json, TEMP_EXP);
			}else {
				setEx(key, json, keyExp);
			}
		}
		return json;
	}
	
	/**
	 * 查询并缓存字符串
	 * 
	 * 1、缓存不存在时，执行 query(param)查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 * 
	 * @param key		缓存KEY和过期时间
	 * @param param		缓存不存在时的查询参数，如id
	 * @param query		缓存不存在时的查询器
	 * 
	 * @return str
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月30日
	 */
	public <P>String getStr(RedisKey key, P param, Function<P,String> query) {
		return getStr(key.getKey(), key.getExp(), param, query);
	}
	
	/**
	 * 查询并缓存数据
	 * 
	 * 1、缓存不存在时，执行 query()查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 * 
	 * @param objClass	数据类型
	 * @param key		缓存KEY
	 * @param keyExp	缓存时间
	 * @param query		缓存不存在时的查询器
	 * 
	 * @return obj
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月30日
	 */
	public <T>T getObj(Class<T> objClass, String key, int keyExp, Supplier<T> query) {
		// 查询缓存
		String json = get(key);
		// 查询DB
		if (json == null) {
			// 查询并存入缓存
			T obj = query.get();
			if (obj == null) {
				setEx(key, StringConstant.EMPTY, TEMP_EXP);
			}else {
				setEx(key, JsonUtil.toStr(obj), keyExp);
			}
			return obj;
		}else {
			// 反序列化
			return JsonUtil.parse(json, objClass);
		}
	}
	
	/**
	 * 查询并缓存数据
	 * 
	 * 1、缓存不存在时，执行 query()查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 * 
	 * @param objClass	数据类型
	 * @param key		缓存KEY和过期时间
	 * @param query		缓存不存在时的查询器
	 * 
	 * @return obj
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月30日
	 */
	public <T>T getObj(Class<T> objClass, RedisKey key, Supplier<T> query) {
		return getObj(objClass, key.getKey(), key.getExp(), query);
	}
	
	/**
	 * 查询并缓存数据
	 * 
	 * 1、缓存不存在时，执行 query(param)查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 * 
	 * @param objClass	数据类型
	 * @param key		缓存KEY
	 * @param keyExp	缓存时间
	 * @param param		缓存不存在时的查询参数，如id
	 * @param query		缓存不存在时的查询器
	 * 
	 * @return obj
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月30日
	 */
	public <P,R>R getObj(Class<R> objClass, String key, int keyExp, P param, Function<P,R> query) {
		// 查询缓存
		String json = get(key);
		// 查询DB
		if (json == null) {
			// 查询并存入缓存
			R obj = query.apply(param);
			if (obj == null) {
				setEx(key, StringConstant.EMPTY, TEMP_EXP);
			}else {
				setEx(key, JsonUtil.toStr(obj), keyExp);
			}
			return obj;
		}else {
			// 反序列化
			return JsonUtil.parse(json, objClass);
		}
	}
	
	/**
	 * 查询并缓存数据
	 * 
	 * 1、缓存不存在时，执行 query(param)查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 * 
	 * @param objClass	数据类型
	 * @param key		缓存KEY和过期时间
	 * @param param		缓存不存在时的查询参数，如id
	 * @param query		缓存不存在时的查询器
	 * 
	 * @return obj
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月30日
	 */
	public <P,R>R getObj(Class<R> objClass, RedisKey key, P param, Function<P,R> query) {
		return getObj(objClass, key.getKey(),  key.getExp(), param, query);
	}
	
	/**
	 * 获取并缓存数据集
	 * 
	 * 1、缓存不存在时，执行 query(ids[i])查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 * 
	 * @param ids		ID列表，缓存KEY为"keyPre+id"
	 * @param keyPre	KEY前缀
	 * @param keyExp	KEY缓存超时
	 * @param query		缓存不存在时的查询器
	 * @return 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月29日
	 */
	public <T>List<String> getStrs(List<T> ids, String keyPre, int keyExp, Function<T, String> query) {
		// 空处理
		if (CollectionUtils.isEmpty(ids)) {
			return ListStr.EMPTY;
		}
		// 整合键
		List<String> keys = new ArrayList<>(ids.size());
		for (T id : ids) {
			keys.add(keyPre + id);
		}
		// 查询缓存
		List<String> strs = get(keys);	
		// 缓存容错处理
		T id;
		String str;
		for (int i=0; i<strs.size(); i++) {
			if(strs.get(i) == null) {
				//获取id
				id = ids.get(i);
				//空处理
				if (id == null) {
					// 临时缓存
					strs.set(i, StringConstant.EMPTY);
					setEx(keyPre, StringConstant.EMPTY, TEMP_EXP);
				}else {
					try {
						// 查询str
						str = query.apply(id);
					} catch (Exception e) {
						// 数据为空
						str = null;
					}
					// 数据为null或str为null
					if (str == null) {
						// 临时缓存
						strs.set(i, StringConstant.EMPTY);
						setEx(keyPre + id, StringConstant.EMPTY, TEMP_EXP);
					}else {
						// 正常缓存
						strs.set(i, str);
						setEx(keyPre + id, str, keyExp);
					}
				}
			}
		}	
		return strs;
	}
	
	/**
	 * 获取并缓存数据集
	 * 
	 * 1、缓存不存在时，执行 query(ids[i])查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 * 
	 * @param ids		ID列表，缓存KEY为"keyPre+id"
	 * @param key		缓存KEY前缀和EXP
	 * @param query		缓存不存在时的查询器
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月29日
	 */
	public <T>List<String> getStrs(List<T> ids, RedisKey key, Function<T, String> query) {
		return getStrs(ids, key.getKey(), key.getExp(), query);
	}

	/**
	 * 获取并缓存数据集
	 *
	 * @param ids		ID列表，缓存KEY为"keyPre+id"
	 * @param keyPre	KEY前缀
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2019年5月29日
	 */
	public <T>Map<T,String> getStrMap(Collection<T> ids, String keyPre) {
		Map<T,String> map = new HashMap<>();
		if (CollectionUtils.isNotEmpty(ids)) {
			// IDS
			List<T> idList = new ArrayList<>();
			List<String> keys = new ArrayList<>();
			for (T id : ids) {
			    idList.add(id);
				keys.add(keyPre + id);
			}
			// 查询
			List<String> values = this.get(keys);
			for (int i = 0; i < idList.size(); i++) {
				map.put(idList.get(i), values.get(i));
			}
		}
		return map;
	}

	/**
	 * 获取并缓存数据集
	 *
	 * 1、缓存不存在时，执行 query(ids[i])查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 *
	 * @param ids		ID列表，缓存KEY为"keyPre+id"
	 * @param keyPre	KEY前缀
	 * @param keyExp	KEY缓存超时
	 * @param query		缓存不存在时的查询器
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2019年5月29日
	 */
	public <T>Map<T,String> getStrMap(Collection<T> ids, String keyPre, int keyExp, Function<T, String> query) {
		Map<T,String> map = new HashMap<>();
		if (CollectionUtils.isNotEmpty(ids)) {
			// 列表去重
			List<T> idList = new ArrayList<>(new HashSet<>(ids));
			// 查询列表
			List<String> names = this.getStrs(idList, keyPre, keyExp, query);
			// 映射
			for (int i = 0; i < idList.size(); i++) {
				map.put(idList.get(i), names.get(i));
			}
		}
		return map;
	}

	/**
	 * 获取并缓存数据集
	 *
	 * 1、缓存不存在时，执行 query(ids[i])查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 *
	 * @param ids		ID列表，缓存KEY为"keyPre+id"
	 * @param key		缓存KEY前缀和EXP
	 * @param query		缓存不存在时的查询器
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2019年5月29日
	 */
	public <T>Map<T,String> getStrMap(Collection<T> ids, RedisKey key, Function<T, String> query) {
		return getStrMap(ids, key.getKey(), key.getExp(), query);
	}

	/**
	 * 获取并缓存数据集
	 * 
	 * 1、缓存不存在时，执行 query(ids[i])查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 * 
	 * @param objClass	数据类型
	 * @param ids		ID列表，缓存KEY为"keyPre+id"
	 * @param keyPre	KEY前缀
	 * @param keyExp	缓存超时
	 * @param query		缓存不存在时的查询器
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月29日
	 */
	public <T,R>List<R> getObjs(Class<R> objClass, List<T> ids, String keyPre, int keyExp, Function<T, R> query) {
		// 空处理
		if (CollectionUtils.isEmpty(ids)) {
			return new ArrayList<>();
		}
		// 整合键
		List<String> keys = new ArrayList<>(ids.size());
		for (T id : ids) {
			keys.add(keyPre + id);
		}
		// 查询缓存
		List<String> vals = get(keys);
		List<R> objs = new ArrayList<>(vals.size());
		// 缓存容错处理
		T id;
		String val;
		R obj;
		for (int i=0; i<vals.size(); i++) {
			val = vals.get(i);
			if (val == null) {
				//获取id
				id = ids.get(i);
				//空处理
				if (id == null) {
					// 临时缓存
					objs.add(null);
					setEx(keyPre, StringConstant.EMPTY, TEMP_EXP);
				}else {
					try {
						// 查询
						obj = query.apply(id);
					} catch (Exception e) {
						// 数据为空
						obj = null;
					}
					// 数据为null
					if (obj == null) {
						// 临时缓存
						objs.add(null);
						setEx(keyPre + id, StringConstant.EMPTY, TEMP_EXP);
					}else {
						// 正常缓存
						objs.add(obj);
						setEx(keyPre + id, JsonUtil.toStr(obj), keyExp);
					}
				}
			}else {
				// 反序列化
				objs.add(JsonUtil.parse(val, objClass));
			}
		}	
		return objs;
	}
	
	/**
	 * 获取并缓存数据集
	 * 
	 * 1、缓存不存在时，执行 query(ids[i])查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 * 
	 * @param objClass	数据类型
	 * @param ids		ID列表，缓存KEY为"keyPre+id"
	 * @param key		缓存KEY前缀和EXP
	 * @param query		缓存不存在时的查询器
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月29日
	 */
	public <T,R>List<R> getObjs(Class<R> objClass, List<T> ids, RedisKey key, Function<T, R> query) {
		return getObjs(objClass, ids, key.getKey(), key.getExp(), query);
	}

	/**
	 * 获取并缓存数据集
	 *
	 * 1、缓存不存在时，执行 query(ids[i])查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 *
	 * @param objClass	数据类型
	 * @param ids		ID列表，缓存KEY为"keyPre+id"
	 * @param keyPre	KEY前缀
	 * @param keyExp	缓存超时
	 * @param query		缓存不存在时的查询器
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2019年5月29日
	 */
	public <T,R>Map<T,R> getObjMap(Class<R> objClass, Collection<T> ids, String keyPre, int keyExp, Function<T, R> query) {
		Map<T,R> map = new HashMap<>();
		if (CollectionUtils.isNotEmpty(ids)) {
			// 列表去重
			List<T> idList = new ArrayList<>(new HashSet<>(ids));
			// 查询列表
			List<R> objs = this.getObjs(objClass, idList, keyPre, keyExp, query);
			// 映射
			for (int i = 0; i < idList.size(); i++) {
				map.put(idList.get(i), objs.get(i));
			}
		}
		return map;
	}

	/**
	 * 获取并缓存数据集
	 *
	 * 1、缓存不存在时，执行 query(ids[i])查询
	 * 2、查询数据存在时，根据过期时间缓存
	 * 3、查询数据不存在时，临时缓存"空字符串"TEMP_EXP秒，防止缓存穿透
	 *
	 * @param objClass	数据类型
	 * @param ids		ID列表，缓存KEY为"keyPre+id"
	 * @param key		缓存KEY前缀和EXP
	 * @param query		缓存不存在时的查询器
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2019年5月29日
	 */
	public <T,R>Map<T,R> getObjMap(Class<R> objClass, Collection<T> ids, RedisKey key, Function<T, R> query) {
		return getObjMap(objClass, ids, key.getKey(), key.getExp(), query);
	}

}